package com.lightsh.network;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.net.SocketFactory;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import android.content.Context;
import android.util.Log;
import com.lightsh.network.exception.NetworkRequestException;
import com.lightsh.network.exception.InvalidTokenException;

public class HttpClient {	
	private final static boolean DEBUG = true;
	
	private static final String TAG = "HttpClient";
	
	public static final String USER_AGENT = "";
	public static final String UTF_8_CODE = "UTF-8";
	
	private final static boolean IGNORE_CERTIFICATE = false;
	
	public static final int WAP_CONNECT_TIME_OUT = 50000;
    public static final int WAP_READ_TIME_OUT = 50000;
    
    public static final int NORMAL_HTTP_TIME_OUT = 30000;

    // 默认的重试次数,即为0时为访问一次(注意这里并非尝试次数)
    public static final int RETRY_COUNT = 1;
    
    private Context mContext;
    private String mBasic = null;    
    private Map<String, String> mRequestHeaders = new HashMap<String, String>();
    private Object mOauth = null;
    private Object mOauthToken = null;
    
    public HttpClient(Context context){
    	mContext = context.getApplicationContext();
    	setUserAgent(USER_AGENT);
    }
    public Response post(String url, PostParameter[] postParameters, Map<String, String> headers,
            boolean authenticated, int retryCount) throws NetworkRequestException, InvalidTokenException {
    	return execRequest(url, postParameters, headers, authenticated, retryCount, NORMAL_HTTP_TIME_OUT);
    }
    public Response post(String url, PostParameter[] postParameters, Map<String, String> headers,
            boolean authenticated) throws NetworkRequestException, InvalidTokenException {
    	return execRequest(url, postParameters, headers, authenticated, RETRY_COUNT, NORMAL_HTTP_TIME_OUT);
	}
    public Response post(String url, PostParameter[] postParameters,
            boolean authenticated) throws NetworkRequestException, InvalidTokenException {
    	return execRequest(url, postParameters, authenticated, RETRY_COUNT);
    }
    public Response post(String url, PostParameter[] postParameters,
            boolean authenticated, int retryCount) throws NetworkRequestException, InvalidTokenException {
    	return execRequest(url, postParameters, authenticated, retryCount);
    }
    public Response post(String url, PostParameter[] postParameters,
            boolean authenticated, int retryCount, int timeOut) throws NetworkRequestException, InvalidTokenException {
    	return execRequest(url, postParameters, null, authenticated, retryCount, timeOut);
	}
    public Response post(String url, boolean authenticated) throws NetworkRequestException, InvalidTokenException {
        return execRequest(url, new PostParameter[0], authenticated, RETRY_COUNT);
    }
    public Response post(String url, PostParameter[] PostParameters) throws NetworkRequestException, InvalidTokenException{
        return execRequest(url, PostParameters, false, RETRY_COUNT);
    }
    public Response post(String url) throws NetworkRequestException, InvalidTokenException {
        return execRequest(url, new PostParameter[0], false, RETRY_COUNT);
    }
    public Response get(String url, boolean authenticated) throws NetworkRequestException, InvalidTokenException {
        return execRequest(url, null, authenticated, RETRY_COUNT);
    }
    public Response get(String url, boolean authenticated, int retryCount) throws NetworkRequestException, InvalidTokenException {
        return execRequest(url, null, authenticated, retryCount);
    }
    public Response get(String url) throws NetworkRequestException, InvalidTokenException {
        return execRequest(url, null, false, RETRY_COUNT);
    }    
    public Response get(String url, boolean authenticated, int retryCount, int timeOut) throws NetworkRequestException, InvalidTokenException {
        return execRequest(url, null, null, authenticated, retryCount, timeOut);
    }
    public Response get(String url, Map<String, String> headers, boolean authenticated, int retryCount) throws NetworkRequestException, InvalidTokenException {
        return execRequest(url, null, null, authenticated, retryCount, NORMAL_HTTP_TIME_OUT);
    }
    
    private Response execRequest(String url, PostParameter[] postParams, boolean authenticated, int retryCount) throws NetworkRequestException, 
		InvalidTokenException {
    	return execRequest(url, postParams, null, authenticated, retryCount, NORMAL_HTTP_TIME_OUT);
    }
    
    private Response execRequest(String url, PostParameter[] postParams, Map<String, String> headers, boolean authenticated, int retryCount, int timeOut) throws NetworkRequestException, InvalidTokenException{
    	Response response = null;
    	int retriedCount;
    	for(retriedCount = 0; retriedCount <= retryCount; ++retriedCount){
    		boolean lastTry = retriedCount == retryCount;
    		if(isProxyEnable() && isHttpsRequest(url)){
        		response = execRequestBySocket(url, postParams, headers, authenticated, lastTry);
        	}else{
        		response = execRequestByAndroidHttpClient(url, postParams, headers, authenticated, timeOut, lastTry);
        	}
    		if(response != null){
    			break; 
    		}
    	}
    	return response;
    }
    private boolean isHttpsRequest(String url){
    	return url != null && url.startsWith("https");
    }
    
    private Response execRequestBySocket(String url, PostParameter[] postParams, Map<String, String> headers, boolean authenticated, boolean throwResponseError) throws NetworkRequestException, InvalidTokenException{
    	Socket sockWap = null;
    	OutputStream out = null;
		InputStream in = null;
    	try{
    		String host = getProxyHost();
    		int port = getProxyPort();
    		boolean shortUrl = false;    		
    		if(host != null){
    			sockWap = SocketFactory.getDefault().createSocket(host, port);
    		}else{
    			String[] result = getHostAndNewUrl(url);
    			host = result[0];
    			if(isHttpsRequest(url)){
    				sockWap = SSLSocketFactory.getDefault().createSocket(host, 443);
    			}else{
    				sockWap = SocketFactory.getDefault().createSocket(host, 80);
    			}
    			shortUrl = true;
    		}
    		
    		sockWap.setSoTimeout(WAP_READ_TIME_OUT);
    		out = sockWap.getOutputStream();
    		in = sockWap.getInputStream();
    		
    		String authorizationString = null;
			if(authenticated){
				authorizationString = getAuthorization(url, false, postParams);
			}
    		
			String helpUrl = shortUrl ? url.substring(url.indexOf(host) + host.length()) : url;
			if(postParams != null){                
                String postParam = encodeParameters(postParams);                
				helpUrl += "?";
				helpUrl += postParam;				
			}
			String outString = null;
			outString = "GET ";
			outString += helpUrl;
			
			outString += " HTTP/1.0\r\nHost: ";
			outString += host;
			outString += "\r\n";
			outString += "Connection: Close\r\n";
			
			for(BasicNameValuePair value : getConstantsHeader(url)){
				outString += value.getName() + ": " + value.getValue() + "\r\n";
			}
			for (String key : mRequestHeaders.keySet()) {
				outString += key + ": " + mRequestHeaders.get(key) + "\r\n";
            }
			if(headers != null && headers.size() > 0){
            	for(Entry<String, String> keyValue : headers.entrySet()){
            		outString += keyValue.getKey() + ": " + keyValue.getValue() + "\r\n";
            	}
            }
			if(authorizationString != null){
				outString += HttpHeaderConstants.COOKIE_AUTHORIZATION + ": " + authorizationString + "\r\n";
			}
            outString += "\r\n";
            
            out.write(outString.getBytes(UTF_8_CODE));
			out.flush();
			
			Response response = readSocketResponse(in);
			if(response.getStatusCode() != Response.RESPONSE_CODE_OK){
				if(response.getStatusCode() == InvalidTokenException.INVALID_TOKEN_RESPONSE_CODE){
					Log.w(TAG, "catch Token invalid response code:" + InvalidTokenException.INVALID_TOKEN_RESPONSE_CODE);
					throw new InvalidTokenException();
				}
				if(response.getStatusCode() == Response.RESPONSE_CODE_PASSWORD_INVALID){
					Log.w(TAG, "catch Token invalid response code:" + Response.RESPONSE_CODE_PASSWORD_INVALID);
					throw new NetworkRequestException(response.asString(), response.getStatusCode());
				}
				if(throwResponseError){
					throw new NetworkRequestException(response.asString(), response.getStatusCode());
				}
			}
			return response;
    	}catch(NetworkRequestException e){
    		throw e;
    	}catch(InvalidTokenException e){
    		throw e;
    	}catch(Exception e){
    		e.printStackTrace();
    	}finally{
    		try{
    			if(out != null){
    				out.close();
    			}
    			if(in != null){
    				in.close();
    			}
				if(sockWap != null){
					sockWap.close();
				}
			}catch(IOException ignore){
				ignore.printStackTrace();
			}
    	}
    	return null;
    }    
    
	private Response readSocketResponse(InputStream in) throws IOException {
		String statusLine = readStatusLine(in);
		
		int statusCode = 0;
		try{
			String[] statusArray = statusLine.split(" ");
			if(statusArray.length == 3){
				statusCode = Integer.valueOf(statusArray[1]);
			}
		}catch(NumberFormatException e){
			e.printStackTrace();
		}
		Map<String, String> headers = readHeaders(in);

		int contentLength = 0;
		try{
			if(headers.get("Content-Length") != null){
				contentLength = Integer.valueOf(headers.get("Content-Length"));
			}
		}catch(NumberFormatException e){
			e.printStackTrace();
		}

		byte[] body = readResponseBody(in, contentLength);
		String charset = headers.get("Content-Type");
		if (charset.matches(".+;charset=.+")) {
			charset = charset.split(";")[1].split("=")[1];
		} else {
			charset = UTF_8_CODE;
		}
		return new Response(new String(body, charset), statusCode);
	}

	private byte[] readResponseBody(InputStream in, int contentLength)
			throws IOException {
		ByteArrayOutputStream buff = new ByteArrayOutputStream(contentLength);
		byte[] byteBuf = new byte[1024];
		int count = 0;
		int read = 0;
		while (count < contentLength && read != -1) {
			read = in.read(byteBuf);
			buff.write(byteBuf, 0, read);
			count += read;
		}
		return buff.toByteArray();
	}

	private Map<String, String> readHeaders(InputStream in) throws IOException {
		Map<String, String> headers = new HashMap<String, String>();
		String line;
		while (!("".equals(line = readLine(in)))) {
			String[] nv = line.split(":");
			headers.put(nv[0].trim(), nv[1].trim());
		}
		return headers;
	}

	private String readStatusLine(InputStream in) throws IOException {
		return readLine(in);
	}

	private String readLine(InputStream in) throws IOException {
		final byte CR = '\r';
		int b;
		ByteArrayOutputStream buff = new ByteArrayOutputStream();
		b = in.read();
		while (b != CR && b != -1) {
			buff.write(b);
			b = in.read();
		}
		in.read(); // skip '\n'
		String line = buff.toString(UTF_8_CODE);
		return line;
	}
    
    private Response execRequestByAndroidHttpClient(String url, PostParameter[] postParams, Map<String, String> headers,
            boolean authenticated, int timeOut, boolean throwResponseError)  throws NetworkRequestException, InvalidTokenException{
    	MAndroidHttpClient client = MAndroidHttpClient.newInstance(USER_AGENT, timeOut, IGNORE_CERTIFICATE);
		try{
			HttpUriRequest httpRequest = null;
			String converUrl = null;
			String converHost = null;
			
			if(isProxyEnable()){
				String[] result = getHostAndNewUrl(url);
				converUrl = result[1];
				converHost = result[0];
			}else{
				converUrl = url;
			}
			
			if(postParams != null){
				HttpPost post = new HttpPost(converUrl);
				
				// 设置post参数 
	            List<NameValuePair> par = new ArrayList<NameValuePair>();
	            for(int i = 0; i < postParams.length; ++i){
	            	par.add(new BasicNameValuePair(postParams[i].name, postParams[i].value));
	            }
				post.setEntity(new UrlEncodedFormEntity(par, UTF_8_CODE));
				
				httpRequest = post;
			}else{
				HttpGet get = new HttpGet(converUrl);
				
				httpRequest = get;
			}
			if(converHost != null){
				httpRequest.setHeader("X-Online-Host", converHost);
			}
			
			if(authenticated){
				String authorization = getAuthorization(url, postParams != null, postParams);
				httpRequest.setHeader(HttpHeaderConstants.COOKIE_AUTHORIZATION, authorization);
			}
			for (String key : mRequestHeaders.keySet()) {
            	httpRequest.setHeader(key, mRequestHeaders.get(key));
            }    			
			// 加上恒定字段
			for(BasicNameValuePair value : getConstantsHeader(url)){
				httpRequest.setHeader(value.getName(), value.getValue());
			}
			
			// use gzip
			httpRequest.setHeader(Response.HEADER_KEY_ACCEPT_CODE, Response.HEADER_ACCEPT_CODE_GZIP);
			
			if(headers != null && headers.size() > 0){
            	for(Entry<String, String> keyValue : headers.entrySet()){
            		httpRequest.setHeader(keyValue.getKey(), keyValue.getValue());
            	}
            }
			
			try{
				HttpResponse response = client.execute(httpRequest);
				if (response.getStatusLine().getStatusCode() == Response.RESPONSE_CODE_OK) {
					HttpEntity entity = response.getEntity();
					InputStream stream = entity.getContent();
					return new Response(response.getStatusLine().getStatusCode(), 
							stream, 
							entity.getContentEncoding() == null ? "" : entity.getContentEncoding().getValue());
				}else{
					// 判断是否是accessToken无效
					if(response.getStatusLine().getStatusCode() == InvalidTokenException.INVALID_TOKEN_RESPONSE_CODE){
						Log.w(TAG, "catch Token invalid response code:" + InvalidTokenException.INVALID_TOKEN_RESPONSE_CODE);
						throw new InvalidTokenException();
					}
					if(response.getStatusLine().getStatusCode() == Response.RESPONSE_CODE_PASSWORD_INVALID){
						Log.w(TAG, "catch Token invalid response code:" + Response.RESPONSE_CODE_PASSWORD_INVALID);
						String errMsg = URLDecoder.decode(EntityUtils.toString(response.getEntity()), UTF_8_CODE);
						throw new NetworkRequestException(errMsg, response.getStatusLine().getStatusCode());
					}
					String errMsg = URLDecoder.decode(EntityUtils.toString(response.getEntity()), UTF_8_CODE);
					int responseCode = response.getStatusLine().getStatusCode();
					log("http request error : " + responseCode + "; " + errMsg);
					if(throwResponseError){
						throw new NetworkRequestException(errMsg, responseCode);
					}
				}				
			}catch(SSLHandshakeException e){
				Log.w(TAG, "catch SSLHandshakeException...");
				throw new NetworkRequestException(e, 0, NetworkRequestException.EXCEPTION_CODE_VALIDATE_TIME);
			}    			
		}catch(NetworkRequestException e){
			throw e;
		}catch(InvalidTokenException e){
			throw e;
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(client != null){
				client.close();
			}
		}
		return null;
    }
    
	private BasicNameValuePair[] getConstantsHeader(String url){
		BasicNameValuePair[] basicHeader = new BasicNameValuePair[]{
    			new BasicNameValuePair(HttpHeaderConstants.COOKIE_LANGUAGE, HttpHeaderConstants.getLanguage()),
    			new BasicNameValuePair(HttpHeaderConstants.COOKIE_NETWORK, HttpHeaderConstants.getNetworkType(mContext))
    	};
		return basicHeader;
    }
    
    private void setUserAgent(String ua) {
    }
    private void setRequestHeader(String name, String value) {
        mRequestHeaders.put(name, value);
    }
    
    /**
     * Sets the consumer key and consumer secret.
     * @param consumerKey Consumer Key
     * @param consumerSecret Consumer Secret
     */
    public void setOAuthConsumer(String consumerKey, String consumerSecret) {
        if (null != consumerKey && null != consumerSecret
                && 0 != consumerKey.length() && 0 != consumerSecret.length()) {
//            mOauth = new OAuth(consumerKey, consumerSecret);
        }
    }
    
    public void setOAuthToken( String token,String tokenSecret){
        if (null != token && null != tokenSecret
                && 0 != token.length() && 0 != tokenSecret.length()) {
//            this.mOauthToken = new OAuthToken(token, tokenSecret);
        }
    }
    public void resetOAuthToken(){
    	this.mOauthToken = null;
    }
    
    public static boolean isProxyEnable(){
    	return NetworkProxy.isProxyEnable();
    }
    public static String getProxyHost(){
    	return NetworkProxy.getProxyHost();
    }
    public static int getProxyPort(){
    	return NetworkProxy.getProxyPort();
    }
    
    /**
     * 根据需要使用的代理获取需要跳转的host和新的url
     * @return string[0] host
     * 		   string[1] newUrl
     */
    public static final String[] getHostAndNewUrl(String url){
    	try{
    		// 解析出host
        	String signFirst = "://";
        	String signSlash = "/";
        	int nPosFirst = url.indexOf(signFirst) + signFirst.length();			//< host的开始位置
        	int nPosSecond = url.indexOf(signSlash, nPosFirst);					//< host后面的"/"位置
        	String host = url.substring(nPosFirst, nPosSecond);
        	
        	String strUrl = url.substring(0, nPosFirst) + HttpClient.getProxyHost();
        	strUrl += url.substring(nPosSecond);    	
        	return new String[]{host, strUrl};
    	}catch(Exception e){
    		e.printStackTrace();
    	}
    	return new String[]{url, url};
    }
    
    public static String encodeParameters(PostParameter[] postParams) {
        StringBuffer buf = new StringBuffer();
        for (int j = 0; j < postParams.length; j++) {
            if (j != 0) {
                buf.append("&");
            }
            try {
                buf.append(URLEncoder.encode(postParams[j].name, UTF_8_CODE))
                        .append("=").append(URLEncoder.encode(postParams[j].value, UTF_8_CODE));
            } catch (java.io.UnsupportedEncodingException neverHappen) {
            }
        }
        return buf.toString();
    }
    
    public String getAuthorization(String url, boolean bPost, PostParameter[] params){
    	String authorization = null;
        if (null != mOauth) {
            // use OAuth
//            authorization = mOauth.generateAuthorizationHeader(bPost ? "POST" : "GET", url, params, mOauthToken);
        } else if (null != mBasic) {
            // use Basic Auth
            authorization = mBasic;
        } else {
        }
        return authorization;
    }
    
    private static void log(String message) {
        if (DEBUG) {
        	Log.d(TAG, "[" + new java.util.Date() + "] " + message);
        }
    }
}
